package com.tianji.learning.utils;

import cn.hutool.core.util.ObjectUtil;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.JsonUtils;
import com.tianji.common.utils.ObjectUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.mapper.LearningLessonMapper;
import com.tianji.learning.mapper.LearningRecordMapper;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.DelayQueue;

@Slf4j
@Component
@RequiredArgsConstructor
public class LearningRecordDelayTaskHandler {
    private final String KEY_TEMPLATE = "learning:record:{}";
    private final StringRedisTemplate redisTemplate;
    private final DelayQueue<DelayTask<DelayTaskData>> delayQueue = new DelayQueue<>();
    private static Boolean flag = true;
    private final LearningLessonMapper lessonMapper;
    private final LearningRecordMapper recordMapper;

    /**
     * 初始化方法
     */
    @PostConstruct
    public void init() {
        log.info("Bean初始化阶段执行");
        CompletableFuture.runAsync(this::handleDelayTask);
    }

    /**
     * 销毁方法
     */
    @PreDestroy
    public void destory() {
        flag = false;
    }

    /**
     * 处理延时检测任务逻辑
     */
    public void handleDelayTask() {
        log.info("开始处理播放进度延时检测任务逻辑...");
        while (flag) {
            try {
                // 1、从延时队列中尝试 take 获取延时检测任务
                DelayTaskData data = delayQueue.take().getData();
                log.info("获取到延时检测任务，任务数据：{}", data);

                // 2、从redis中查询实时的播放进度
                LearningRecord learningRecord = readLearningRecordFromCache(data.getLessonId(), data.getSectionId());

                // 3、比较延时任务重的本地进度 与 redis中的实时进度 是否一致
                // 3.1、如果不一致，证明用户还在观看，直接跳过
                if (ObjectUtils.isEmpty(learningRecord) || ObjectUtils.notEqual(learningRecord.getMoment(), data.getMoment())) {
                    continue;
                }

                // 3.2、如果一致，则说明用户已经停止观看，需要将redis中的实时进度 更新到学习记录表中，并更新 课表 数据
                // 3.2.1、更新学习记录表（主要是更新 播放进度 moment）
                learningRecord.setFinished(null);
                recordMapper.updateById(learningRecord);

                // 3.2.2、更新课表数据（更新 最近学习小节ID、最近学习时间）
                LearningLesson lesson = LearningLesson.builder()
                        .id(data.lessonId)
                        .latestLearnTime(LocalDateTime.now().minusSeconds(20))
                        .latestSectionId(data.sectionId)
                        .build();
                lessonMapper.updateById(lesson);

            } catch (Exception e) {
                log.error("处理某个播放进度延时检测任务逻辑异常", e);
            }


        }
    }

    /**
     * 从缓存中获取学习记录
     *
     * @param lessonId
     * @param sectionId
     * @return
     */
    public LearningRecord readLearningRecordFromCache(Long lessonId, Long sectionId) {
        log.info("从缓存中获取学习记录：lessonId={}, sectionId={}", lessonId, sectionId);
        // 1、组装大key learning:record:{}
        String formatKey = StringUtils.format(KEY_TEMPLATE, lessonId);

        // 2、组装hashKey
        String hashKey = lessonId.toString();

        // 3、发起查询，得到hashvalue
        String recordStr = (String) redisTemplate.opsForHash().get(formatKey, hashKey);
        if (ObjectUtil.isEmpty(recordStr)) {
            log.warn("缓存中没有查询到学习记录，课表ID：{}，小节ID：{}", lessonId, sectionId);
            return null;
        }
        // 4、将 hashvalue 转为 LearningRecord
        return JsonUtils.toBean(recordStr, LearningRecord.class);
    }


    /**
     * 将学习记录写入缓存
     *
     * @param oldRecord
     */
    public void writeLearningRecordToCache(LearningRecord oldRecord) {
        log.info("将学习记录写入缓存，学习记录数据：{}", oldRecord);
        // 1、组装大key learning:record:{}
        String formatKey = StringUtils.format(KEY_TEMPLATE, oldRecord.getLessonId());

        // 2、组装hashkey
        String hashKey = oldRecord.getLessonId().toString();

        // 3、组装hashvalue
        String jsonStr = JsonUtils.toJsonStr(BeanUtils.toBean(oldRecord, RedisRecordData.class));

        // 4、写入缓存
        redisTemplate.opsForHash().put(formatKey, hashKey, jsonStr);
        redisTemplate.expire(formatKey, Duration.ofMinutes(1));

    }

    /**
     * 清除学习记录缓存
     *
     * @param sectionId
     * @param lessonId
     */
    public void cleanLearningRecordCache(Long sectionId, Long lessonId) {
        log.info("清除学习记录缓存，课表ID：{}，小节ID：{}", lessonId, sectionId);
        // 1、组装大key learning:record:{}
        String formatKey = StringUtils.format(KEY_TEMPLATE, lessonId);
        // 2、组装hashkey
        String hashKey = lessonId.toString();
        // 3、删除缓存
        redisTemplate.opsForHash().delete(formatKey, hashKey);
    }

    /**
     * 实时更新学习进度 & 添加学习记录延时检测任务
     *
     * @param learningRecord
     */
    public void addLearningRecordDelayCheckTask(LearningRecord learningRecord) {
        log.info("实时更新学习进度 & 提交延时检测任务，学习记录数据：{}", learningRecord);
        // 1、暂存播放记录到redis
        writeLearningRecordToCache(learningRecord);
        // 2、提交延时检测任务
        delayQueue.add(DelayTask.of(Duration.ofSeconds(20), BeanUtils.toBean(learningRecord, DelayTaskData.class)));

    }

    /**
     * 根据课表ID 获取最新学习记录（进度）
     *
     * @param id
     * @return
     */
    public Map<Long, LearningRecord> getLearningRecord4LessonId(Long id) {
        log.info("根据课表ID获取 实时学习记录，课表ID：{}", id);
        // 1、组装大key learning:record:{}
        String formatKey = StringUtils.format(KEY_TEMPLATE, id);

        // 2、获取key对应的所有学习记录
        Map<Long,LearningRecord> learningReordMap = new HashMap<>();
        Map<Object, Object> entries = redisTemplate.opsForHash().entries(formatKey);
        entries.forEach((k, v) -> {
            LearningRecord record = BeanUtils.toBean(v, LearningRecord.class);
            learningReordMap.put((Long) k, record);
        });

        return learningReordMap;
    }


    @Data
    class RedisRecordData {
        /**
         * 学习记录的id
         */
        private Long id;

        /**
         * 视频的当前观看时间点，单位秒
         */
        private Integer moment;

        /**
         * 是否完成学习，默认false
         */
        private Boolean finished;
    }

    @Data
    class DelayTaskData {
        /**
         * 课表ID
         */
        private Long lessonId;
        /**
         * 小节ID
         */
        private Long sectionId;
        /**
         * 视频的当前观看时间点，单位秒
         */
        private Integer moment;
    }
}
